package com.wuyou.demo;

import net.sourceforge.tess4j.ITesseract;
import net.sourceforge.tess4j.Tesseract;
import net.sourceforge.tess4j.TesseractException;
import org.opencv.core.Core;
import org.opencv.core.Mat;
import org.opencv.core.Size;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;

import java.io.File;

/**
 * OCR
 *
 * @Date 2025/6/5 18:00
 * @Author 无忧
 */

public class ContainerNumberOCR {

    static {
        // 加载OpenCV本地库
        System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
    }

    public static void main(String[] args) {
        String imagePath = "D:\\Documents\\Desktop\\4431195631_2042497995.jpg";  // 替换为你的图片路径

        // 1. 图像预处理
        String processedImagePath = preprocessImage(imagePath);

        // 2. OCR识别
        String result = recognizeContainerNumber(processedImagePath);

        // 3. 集装箱号格式验证
        String containerNumber = validateContainerNumber(result);

        System.out.println("识别结果: " + result);
        System.out.println("验证后的集装箱号: " + (containerNumber != null ? containerNumber : "未识别到有效箱号"));
    }

    /**
     * 图像预处理 - 优化集装箱号识别
     */
    private static String preprocessImage(String imagePath) {
        Mat src = Imgcodecs.imread(imagePath, Imgcodecs.IMREAD_COLOR);

        // 转换为灰度图
        Mat gray = new Mat();
        Imgproc.cvtColor(src, gray, Imgproc.COLOR_BGR2GRAY);

        // 高斯模糊降噪
        Mat blurred = new Mat();
        Imgproc.GaussianBlur(gray, blurred, new Size(3, 3), 0);

        // 自适应阈值二值化
        Mat binary = new Mat();
        Imgproc.adaptiveThreshold(blurred, binary, 255, Imgproc.ADAPTIVE_THRESH_GAUSSIAN_C, Imgproc.THRESH_BINARY, 11, 2);

        // 形态学操作 - 闭运算连接字符
        Mat kernel = Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size(3, 3));
        Imgproc.morphologyEx(binary, binary, Imgproc.MORPH_CLOSE, kernel);

        // 保存处理后的图像
        String processedPath = imagePath.replace(".", "_processed.");
        Imgcodecs.imwrite(processedPath, binary);

        return processedPath;
    }

    /**
     * 使用Tesseract进行OCR识别
     */
    private static String recognizeContainerNumber(String imagePath) {
        ITesseract tesseract = new Tesseract();
        try {
            // 设置Tesseract数据路径（根据你的安装位置修改）
            tesseract.setDatapath("tessdata");

            // 设置只识别大写字母和数字
            tesseract.setTessVariable("tessedit_char_whitelist", "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789");

            // 设置识别模式
            tesseract.setPageSegMode(7);  // 单行文本模式

            // 执行OCR
            return tesseract.doOCR(new File(imagePath)).replaceAll("\\s+", "")  // 移除所有空白字符
                    .toUpperCase();
        } catch (TesseractException e) {
            System.err.println("OCR识别错误: " + e.getMessage());
            return "";
        }
    }

    /**
     * 验证和提取标准集装箱号
     * 格式：4个字母 + 6位数字 + 1位校验码
     */
    private static String validateContainerNumber(String text) {
        // 提取所有可能的11位连续字符（标准箱号长度）
        String candidate = text.replaceAll("[^A-Z0-9]", "");

        // 检查长度
        if (candidate.length() < 11) {
            return null;
        }

        // 查找可能的集装箱号
        for (int i = 0; i <= candidate.length() - 11; i++) {
            String segment = candidate.substring(i, i + 11);

            // 验证格式：前4位字母 + 后7位数字
            if (segment.matches("^[A-Z]{4}\\d{7}$")) {
                // 验证校验码（可选）
                if (validateCheckDigit(segment)) {
                    return segment;
                }
            }
        }

        return null;
    }

    /**
     * ISO 6346 集装箱校验码验证
     */
    private static boolean validateCheckDigit(String containerNumber) {
        String base = containerNumber.substring(0, 10);
        char checkDigit = containerNumber.charAt(10);

        int sum = 0;
        for (int i = 0; i < base.length(); i++) {
            int charValue;
            char c = base.charAt(i);

            if (Character.isLetter(c)) {
                // A=10, B=12, ..., Z=38 (跳过11,22,33)
                charValue = c - 'A' + 10;
                charValue += (charValue / 11);  // 跳过11的倍数
            } else {
                charValue = Character.getNumericValue(c);
            }

            // 权重：2^0, 2^1, ... 2^9
            sum += charValue * (1 << i);
        }

        int calculatedDigit = sum % 11;
        calculatedDigit = (calculatedDigit == 10) ? 0 : calculatedDigit;

        return Character.getNumericValue(checkDigit) == calculatedDigit;
    }
}
